package com.itplh.modules.shirorealm.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Apache Shiro 核心通过 Filter 来实现，就好像SpringMvc 通过DispachServlet 来主控制一样。
 * 既然是使用 Filter 一般也就能猜到，是通过URL规则来进行过滤和权限校验，所以我们需要定义一系列关于URL的规则和访问权限。
 *
 * @Description:
 * @Author: tanpeng
 * @Date: 2019-07-11 11:12
 * @Version: V1.0
 *
 * Shiro框架简介
 *
 * Apache Shiro 是Java中的一个简单易上手,容易进行扩展的权限校验框架.相比于Spring Security而言,Shiro显得更加的轻巧.
 * ( PS:未具体研究Spring Security )
 * 当然了,轻巧的同时,也就意味着功能要比Spring Security框架要少的多.但是已经能满足我日常开发所需了,所以就使用它了,合适才是最好的.
 *
 *
 * Primary Concerns部分是整个Shiro框架的基础,四个模块分别代表着身份验证、授权、回话管理、加密.
 *
 *  Authentication：身份认证/登录，验证用户是不是拥有相应的身份；
 *  Authorization：授权，即权限验证，验证某个已认证的用户是否拥有某个权限；即判断用户是否能做事情，常见的如：验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限；
 *  Session Manager：会话管理，即用户登录后就是一次会话，在没有退出之前，它的所有信息都在会话中；会话可以是普通JavaSE环境的，也可以是如Web环境的；
 *  Cryptography：加密，保护数据的安全性，如密码加密存储到数据库，而不是明文存储；
 *
 * Supporting Features模块则是说明Shiro框架还具有其他的扩展功能. 下面分别来介绍一下它的扩展功能.
 *
 *  Web Support：Web支持，可以非常容易的集成到Web环境；
 *  Caching：缓存，比如用户登录后，其用户信息、拥有的角色/权限不必每次去查，这样可以提高效率；
 *  Concurrency：shiro支持多线程应用的并发验证，即如在一个线程中开启另一个线程，能把权限自动传播过去；
 *  Testing：提供测试支持；
 *  Run As：允许一个用户假装为另一个用户（如果他们允许）的身份进行访问；
 *  Remember Me：记住我，这个是非常常见的功能，即一次登录后，下次再来的话不用登录了。
 *
 *
 *  其实在什么服务器语言之中都存在着权限校验这种问题,那么其中就存在了三个最终的部分,分别是Subject、SecurityManager、Realm三个部分.
 *
 *  Subject :主体，既可以代表用户，也可以代表程序（网络爬虫等），它需要访问系统，系统则需要对其进行认证和授权，可以看到主体可以是任何可以与应用交互的“用户”。
 *  SecurityManager :安全管理，用户请求Url，对应于一个Subject对象，由SecurityManager统一对Subject进行认证和授权（父）。
 *  Realm : 用于存储验证数据的数据库.是验证Subject权限的重要凭证.
 *
 *  用文字这么来表述,通过前端或者移动端传入的用户信息,包含用户账号和用户凭证(用户密码),
 *  然后组成UserNamePasswordToken,也就是主体部分.然后把主体部分传入Security Manager模块中.
 *  首先,Security Manager模块会根据Token信息去查询Realm,查看是否存在该用户信息,
 *  如果不存在抛出用户不存在异常,如果存在进行下一步操作,那就是比对用户凭证信息,这一步也是需要查看Realm中的用户信息,
 *  如果Token中的用户凭证和Realm中的用户凭证一致,那么验证通过,否则验证失败.
 *
 */
@Configuration
public class ShiroConfig {

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意：单独一个ShiroFilterFactoryBean配置是或报错的，因为在
     * 初始化ShiroFilterFactoryBean的时候需要注入：SecurityManager
     *
     * Filter Chain定义说明
     * 1、一个URL可以配置多个Filter，使用逗号分隔
     * 2、当设置多个过滤器时，全部验证通过，才视为通过
     * 3、部分过滤器可指定参数，如perms，roles
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // setLoginUrl 如果不设置值，默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
        // 没有登录授权时跳转到该接口
        shiroFilterFactoryBean.setLoginUrl("/shirorealm/notLogin");
        // 没有角色权限时跳转到该接口
        shiroFilterFactoryBean.setUnauthorizedUrl("/shirorealm/notRole");

        // 设置拦截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //游客，开发权限
        filterChainDefinitionMap.put("/shirorealm/guest/**", "anon");
        //用户，需要角色权限 “user”
        filterChainDefinitionMap.put("/shirorealm/user/**", "roles[user]");
        //管理员，需要角色权限 “admin”
        filterChainDefinitionMap.put("/shirorealm/admin/**", "roles[admin]");
        //开放登陆接口
        filterChainDefinitionMap.put("/shirorealm/login", "anon");
        //其余接口一律拦截
        //主要这行代码必须放在所有权限设置的最后，不然会导致所有 url 都被拦截
//        filterChainDefinitionMap.put("/**", "authc");

        // 过滤连定义，从上向下顺序执行，一般将/**放在最为下边 ！！！！！
        // authc：所有的url都必须认证通过才可以访问；anon:所有的url都可以匿名访问
        filterChainDefinitionMap.put("/**", "anon");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro拦截器工厂类注入成功");
        return shiroFilterFactoryBean;
    }

    /**
     * 注入 securityManager
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    /**
     * 自定义身份认证 realm;
     * <p>
     * 必须写这个类，并加上 @Bean 注解，目的是注入 CustomRealm，
     * 否则会影响 CustomRealm类 中其他类的依赖注入
     */
    @Bean
    public CustomRealm customRealm() {
        return new CustomRealm();
    }
}
